﻿/************************************************************************

   Extended WPF Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license 

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. The commercial version of Extended WPF Toolkit also includes
   priority technical support, commercial updates, and many additional 
   useful WPF controls if you license Xceed Business Suite for WPF.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace Twi.COMMON.WPF.CtlExtended
{
    public class TimePicker : System.Windows.Controls.Control
  {
    #region Members

    ListBox _timeListBox;
    private DateTimeFormatInfo DateTimeFormatInfo
    {
      get;
      set;
    }
    internal static readonly TimeSpan EndTimeDefaultValue = new TimeSpan( 23, 59, 0 );
    internal static readonly TimeSpan StartTimeDefaultValue = new TimeSpan( 0, 0, 0 );
    internal static readonly TimeSpan TimeIntervalDefaultValue = new TimeSpan( 1, 0, 0 );

    #endregion //Members

    #region Properties

    #region AllowSpin

    public static readonly DependencyProperty AllowSpinProperty = DependencyProperty.Register( "AllowSpin", typeof( bool ), typeof( TimePicker ), new UIPropertyMetadata( true ) );
    public bool AllowSpin
    {
      get
      {
        return ( bool )GetValue( AllowSpinProperty );
      }
      set
      {
        SetValue( AllowSpinProperty, value );
      }
    }

    #endregion //AllowSpin

    #region EndTime

    public static readonly DependencyProperty EndTimeProperty = DependencyProperty.Register( "EndTime", typeof( TimeSpan ), typeof( TimePicker ), new UIPropertyMetadata( EndTimeDefaultValue, new PropertyChangedCallback( OnEndTimeChanged ), new CoerceValueCallback( OnCoerceEndTime ) ) );

    private static object OnCoerceEndTime( DependencyObject o, object value )
    {
      TimePicker timePicker = o as TimePicker;
      if( timePicker != null )
        return timePicker.OnCoerceEndTime( ( TimeSpan )value );
      else
        return value;
    }

    private static void OnEndTimeChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
    {
      TimePicker timePicker = o as TimePicker;
      if( timePicker != null )
        timePicker.OnEndTimeChanged( ( TimeSpan )e.OldValue, ( TimeSpan )e.NewValue );
    }

    protected virtual TimeSpan OnCoerceEndTime( TimeSpan value )
    {
      // TODO: Keep the proposed value within the desired range.
      return value;
    }

    protected virtual void OnEndTimeChanged( TimeSpan oldValue, TimeSpan newValue )
    {
      // TODO: Add your property changed side-effects. Descendants can override as well.
    }

    public TimeSpan EndTime
    {
      // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
      get
      {
        return ( TimeSpan )GetValue( EndTimeProperty );
      }
      set
      {
        SetValue( EndTimeProperty, value );
      }
    }


    #endregion //EndTime

    #region Format

    public static readonly DependencyProperty FormatProperty = DependencyProperty.Register( "Format", typeof( TimeFormat ), typeof( TimePicker ), new UIPropertyMetadata( TimeFormat.ShortTime, OnFormatChanged ) );
    public TimeFormat Format
    {
      get
      {
        return ( TimeFormat )GetValue( FormatProperty );
      }
      set
      {
        SetValue( FormatProperty, value );
      }
    }

    private static void OnFormatChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
    {
      TimePicker timePicker = o as TimePicker;
      if( timePicker != null )
        timePicker.OnFormatChanged( ( TimeFormat )e.OldValue, ( TimeFormat )e.NewValue );
    }

    protected virtual void OnFormatChanged( TimeFormat oldValue, TimeFormat newValue )
    {

    }

    #endregion //Format

    #region FormatString

    public static readonly DependencyProperty FormatStringProperty = DependencyProperty.Register( "FormatString", typeof( string ), typeof( TimePicker ), new UIPropertyMetadata( default( String ), OnFormatStringChanged ) );
    public string FormatString
    {
      get
      {
        return ( string )GetValue( FormatStringProperty );
      }
      set
      {
        SetValue( FormatStringProperty, value );
      }
    }

    private static void OnFormatStringChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
    {
      TimePicker timePicker = o as TimePicker;
      if( timePicker != null )
        timePicker.OnFormatStringChanged( ( string )e.OldValue, ( string )e.NewValue );
    }

    protected virtual void OnFormatStringChanged( string oldValue, string newValue )
    {
      if( string.IsNullOrEmpty( newValue ) )
        throw new ArgumentException( "CustomFormat should be specified.", FormatString );
    }

    #endregion //FormatString

    #region IsOpen

    public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register( "IsOpen", typeof( bool ), typeof( TimePicker ), new UIPropertyMetadata( false ) );
    public bool IsOpen
    {
      get
      {
        return ( bool )GetValue( IsOpenProperty );
      }
      set
      {
        SetValue( IsOpenProperty, value );
      }
    }

    #endregion //IsOpen

    #region Maximum



    #endregion //Maximum

    #region Minimum



    #endregion //Minimum

    #region ShowButtonSpinner

    public static readonly DependencyProperty ShowButtonSpinnerProperty = DependencyProperty.Register( "ShowButtonSpinner", typeof( bool ), typeof( TimePicker ), new UIPropertyMetadata( true ) );
    public bool ShowButtonSpinner
    {
      get
      {
        return ( bool )GetValue( ShowButtonSpinnerProperty );
      }
      set
      {
        SetValue( ShowButtonSpinnerProperty, value );
      }
    }

    #endregion //ShowButtonSpinner

    #region StartTime

    public static readonly DependencyProperty StartTimeProperty = DependencyProperty.Register( "StartTime", typeof( TimeSpan ), typeof( TimePicker ), new UIPropertyMetadata( StartTimeDefaultValue, new PropertyChangedCallback( OnStartTimeChanged ), new CoerceValueCallback( OnCoerceStartTime ) ) );

    private static object OnCoerceStartTime( DependencyObject o, object value )
    {
      TimePicker timePicker = o as TimePicker;
      if( timePicker != null )
        return timePicker.OnCoerceStartTime( ( TimeSpan )value );
      else
        return value;
    }

    private static void OnStartTimeChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
    {
      TimePicker timePicker = o as TimePicker;
      if( timePicker != null )
        timePicker.OnStartTimeChanged( ( TimeSpan )e.OldValue, ( TimeSpan )e.NewValue );
    }

    protected virtual TimeSpan OnCoerceStartTime( TimeSpan value )
    {
      // TODO: Keep the proposed value within the desired range.
      return value;
    }

    protected virtual void OnStartTimeChanged( TimeSpan oldValue, TimeSpan newValue )
    {
      // TODO: Add your property changed side-effects. Descendants can override as well.
    }

    public TimeSpan StartTime
    {
      // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
      get
      {
        return ( TimeSpan )GetValue( StartTimeProperty );
      }
      set
      {
        SetValue( StartTimeProperty, value );
      }
    }


    #endregion //StartTime

    #region TimeInterval

    public static readonly DependencyProperty TimeIntervalProperty = DependencyProperty.Register( "TimeInterval", typeof( TimeSpan ), typeof( TimePicker ), new UIPropertyMetadata( TimeIntervalDefaultValue, OnTimeIntervalChanged ) );
    public TimeSpan TimeInterval
    {
      get
      {
        return ( TimeSpan )GetValue( TimeIntervalProperty );
      }
      set
      {
        SetValue( TimeIntervalProperty, value );
      }
    }

    private static void OnTimeIntervalChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
    {
      TimePicker timePicker = o as TimePicker;
      if( timePicker != null )
        timePicker.OnTimeIntervalChanged( ( TimeSpan )e.OldValue, ( TimeSpan )e.NewValue );
    }

    protected virtual void OnTimeIntervalChanged( TimeSpan oldValue, TimeSpan newValue )
    {
      // TODO: Add your property changed side-effects. Descendants can override as well.
    }

    #endregion //TimeInterval

    #region Value

    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( "Value", typeof( DateTime? ), typeof( TimePicker ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged ) );
    public DateTime? Value
    {
      get
      {
        return ( DateTime? )GetValue( ValueProperty );
      }
      set
      {
        SetValue( ValueProperty, value );
      }
    }

    private static void OnValueChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
    {
      TimePicker timePicker = o as TimePicker;
      if( timePicker != null )
        timePicker.OnValueChanged( ( DateTime? )e.OldValue, ( DateTime? )e.NewValue );
    }

    protected virtual void OnValueChanged( DateTime? oldValue, DateTime? newValue )
    {
      //TODO: refactor this
      if( newValue.HasValue && _timeListBox != null )
      {
        var items = _timeListBox.ItemsSource;
        foreach( TimeItem item in items )
        {
          if( item.Time == newValue.Value.TimeOfDay )
          {
            int index = _timeListBox.Items.IndexOf( item );
            if( _timeListBox.SelectedIndex != index )
              _timeListBox.SelectedIndex = index;
            break;
          }
        }
      }

      RoutedPropertyChangedEventArgs<object> args = new RoutedPropertyChangedEventArgs<object>( oldValue, newValue );
      args.RoutedEvent = ValueChangedEvent;
      RaiseEvent( args );
    }

    #endregion //Value

    #region Watermark

    public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register( "Watermark", typeof( object ), typeof( TimePicker ), new UIPropertyMetadata( null ) );
    public object Watermark
    {
      get
      {
        return ( object )GetValue( WatermarkProperty );
      }
      set
      {
        SetValue( WatermarkProperty, value );
      }
    }

    #endregion //Watermark

    #region WatermarkTemplate

    public static readonly DependencyProperty WatermarkTemplateProperty = DependencyProperty.Register( "WatermarkTemplate", typeof( DataTemplate ), typeof( TimePicker ), new UIPropertyMetadata( null ) );
    public DataTemplate WatermarkTemplate
    {
      get
      {
        return ( DataTemplate )GetValue( WatermarkTemplateProperty );
      }
      set
      {
        SetValue( WatermarkTemplateProperty, value );
      }
    }

    #endregion //WatermarkTemplate

    #endregion //Properties

    #region Constructors

    static TimePicker()
    {
      DefaultStyleKeyProperty.OverrideMetadata( typeof( TimePicker ), new FrameworkPropertyMetadata( typeof( TimePicker ) ) );
    }

    public TimePicker()
    {
      DateTimeFormatInfo = DateTimeFormatInfo.GetInstance( CultureInfo.CurrentCulture );
      Keyboard.AddKeyDownHandler( this, OnKeyDown );
      System.Windows.Input.Mouse.AddPreviewMouseDownOutsideCapturedElementHandler(this, OnMouseDownOutsideCapturedElement);
    }

    #endregion //Constructors

    #region Base Class Overrides

    public override void OnApplyTemplate()
    {
      base.OnApplyTemplate();

      if( _timeListBox != null )
        _timeListBox.SelectionChanged -= TimeListBox_SelectionChanged;

      _timeListBox = GetTemplateChild( "PART_TimeListItems" ) as ListBox;

      if( _timeListBox != null )
      {
        _timeListBox.SelectionChanged += TimeListBox_SelectionChanged;
        _timeListBox.ItemsSource = GenerateTimeListItemsSource();
      }
    }

    #endregion //Base Class Overrides

    #region Event Handlers

    private void OnKeyDown( object sender, KeyEventArgs e )
    {
      switch( e.Key )
      {
        case Key.Escape:
        case Key.Tab:
          {
            CloseTimePicker();
            break;
          }
      }
    }

    private void OnMouseDownOutsideCapturedElement( object sender, MouseButtonEventArgs e )
    {
      CloseTimePicker();
    }

    void TimeListBox_SelectionChanged( object sender, SelectionChangedEventArgs e )
    {
      if( e.AddedItems.Count > 0 )
      {
        TimeItem selectedTimeListItem = ( TimeItem )e.AddedItems[ 0 ];
        var time = selectedTimeListItem.Time;
        ;
        var date = Value ?? DateTime.MinValue;

        Value = new DateTime( date.Year, date.Month, date.Day, time.Hours, time.Minutes, time.Seconds, time.Milliseconds );
      }

      CloseTimePicker();
    }

    #endregion //Event Handlers

    #region Events

    //Due to a bug in Visual Studio, you cannot create event handlers for nullable args in XAML, so I have to use object instead.
    public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent( "ValueChanged", RoutingStrategy.Bubble, typeof( RoutedPropertyChangedEventHandler<object> ), typeof( TimePicker ) );
    public event RoutedPropertyChangedEventHandler<object> ValueChanged
    {
      add
      {
        AddHandler( ValueChangedEvent, value );
      }
      remove
      {
        RemoveHandler( ValueChangedEvent, value );
      }
    }

    #endregion //Events

    #region Methods

    private void CloseTimePicker()
    {
      if( IsOpen )
        IsOpen = false;
      ReleaseMouseCapture();
    }

    public IEnumerable GenerateTimeListItemsSource()
    {
      TimeSpan time = StartTime;
      TimeSpan endTime = EndTime;

      if( endTime <= time )
      {
        endTime = EndTimeDefaultValue;
        time = StartTimeDefaultValue;
      }

      TimeSpan timeInterval = TimeInterval;

      if( time != null && endTime != null && timeInterval != null && timeInterval.Ticks > 0 )
      {
        while( time <= endTime )
        {
          yield return new TimeItem( DateTime.MinValue.Add( time ).ToString( GetTimeFormat(), CultureInfo.CurrentCulture ), time );
          time = time.Add( timeInterval );
        }
        yield break;
      }
    }

    private string GetTimeFormat()
    {
      switch( Format )
      {
        case TimeFormat.Custom:
          return FormatString;
        case TimeFormat.LongTime:
          return DateTimeFormatInfo.LongTimePattern;
        case TimeFormat.ShortTime:
          return DateTimeFormatInfo.ShortTimePattern;
        default:
          return DateTimeFormatInfo.ShortTimePattern;
      }
    }

    #endregion //Methods
  }
}
